package com.dx.gate.server;

import com.dx.chat.kafka.config.service.IKafkaProducerService;
import com.dx.gate.common.cnst.UserProtocalsType;
import com.dx.gate.service.ImWorkerManager;
import com.dx.mobileimsdk.server.event.ServerEventListener;
import com.dx.mobileimsdk.server.protocal.Protocal;
import io.netty.channel.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
//import org.springframework.kafka.core.KafkaTemplate;

/**
 * 与客户端的所有数据交互事件（实现ServerEventListener类）
 *
 * @author xufeng
 * @since 21.03.2023
 */
@Component
@Slf4j
public class ChatServerEventListener implements ServerEventListener {
    //    private static ChatServerEventListener instance = null;
//
//    public static ChatServerEventListener getInstance() {
//        if(instance == null){
//            instance = new ChatServerEventListener();
//        }
//
//        return instance;
//    }
    private final IKafkaProducerService iKafkaProducerService;
    private ImWorkerManager imWorkerManager;

    public ChatServerEventListener(IKafkaProducerService iKafkaProducerService, ImWorkerManager imWorkerManager) {
        this.iKafkaProducerService = iKafkaProducerService;
        this.imWorkerManager = imWorkerManager;
    }



    /**
     * 用户的IM连接/登陆请求身份验证回调方法定义.
     * <p>
     * 服务端的应用层可在本方法中实现用户登陆验证。
     * <br>
     * 注意：本回调在一种特殊情况下——即用户实际未退出登陆但再次发起来登陆包时，本回调是不会被调用的！
     * <p>
     * 根据MobileIMSDK的算法实现，本方法中用户验证通过（即方法返回值=0时）后
     * ，将立即调用回调方法 {@link #onUserLoginSucess(String, String, Channel)}。
     * 否则会将验证结果（本方法返回值错误码通过客户端的 ChatBaseEvent.onLoginMessage(int dwUserId, int dwErrorCode)
     * 方法进行回调）通知客户端）。
     *
     * @param userId  传递过来的准一id，保证唯一就可以通信，可能是登陆用户名、也可能是任意不重复的id等，具体意义由业务层决定
     * @param token   用于身份鉴别和合法性检查的token，它可能是登陆密码，也可能是通过前置单点登陆接口拿到的token等，具体意义由业务层决定
     * @param extra   额外信息字符串。本字段目前为保留字段，供上层应用自行放置需要的内容
     * @param session 此客户端连接对应的会话
     * @return 0 表示登陆验证通过，否则可以返回用户自已定义的错误码，错误码值应为：>=1025的整数
     */
//    @Override
    public int onUserLoginVerify(String userId, String token, String extra, Channel session) {
        log.info("登陆认证");
        return 0;
//        try {
//            // 证认成功
//            if (LogicProcessor.userAuthentication(userId, token)) {
//                userID.setInt(0, id);
//                id++;
//                logger.debug("用户身份验证--用户名：" + lpUserName + " 密码：" + lpPassword + " 昵称：" + lpNickName + "");
//                return 0;
//            }
//            return 200; // 认证失败，用户名或密码有误
//        } catch (Exception e) {
//            return 200;
//        }
    }

    /**
     * 用户的IM连接/登陆请求验证成功后的回调方法定义（即：上线通知）.
     * <p>
     * 服务端的应用层通常可在本方法中实现用户上线通知等。
     * <br>
     * 注意：本回调在一种特殊情况下——即用户实际未退出登陆但再次发起来登陆包时，回调也是一定会被调用。
     *
     * @param userId  传递过来的准一id，保证唯一就可以通信，可能是登陆用户名、也可能是任意不重复的id等，具体意义由业务层决定
     * @param extra   额外信息字符串。本字段目前为保留字段，供上层应用自行放置需要的内容。为了丰富应用层处理的手段，在本回调中也把此字段传进来了
     * @param session 成功登陆后建立的MINA会话
     */
    @Override
    public void onUserLoginSucess(String userId, String extra, Channel session) {
        log.info("[RBChat]用户连接IM服务器成功--用户ID:" + userId + ",远程IP:"
                + session.remoteAddress().toString());
        //用户节点信息管理
        imWorkerManager.register(userId);



//        String topic = TOPIC_NAME;
//        Map<String, Object> msg = new HashMap<>();
//
//        String msgJson = new Gson().toJson(msg);
//        //消息投递到MQ
//        ListenableFuture<SendResult<String, Object>> listenableFuture = kafkaTemplate.send(topic, UUID.randomUUID().toString(), msgJson);
//        //发送成功后回调
//        SuccessCallback successCallback = result -> log.debug("发送成功:{}", msgJson);
//        //发送失败回调
//        FailureCallback failureCallback = ex -> log.error("发送失败", msgJson, ex);
//        listenableFuture.addCallback(successCallback, failureCallback);

        //ChatLogicManager.processUserLoginAction(userId, extra, session);

    }

    /**
     * 用户退出IM连接的回调方法定义（即：下线通知）。
     * <p>
     * 服务端的应用层通常可在本方法中实现用户下线通知等。
     *
     * @param session       此客户端连接对应的 netty “会话”
     * @param beKickoutCode 被踢原因编码，本参数当为-1时表示本次logout事件不是源自“被踢”，否则被踢原因编码请见 {@link com.dx.mobileimsdk.server.protocal.s.PKickoutInfo}类中的常量定义
     * @param session       此客户端连接对应的MINA会话
     */
    @Override
    public void onUserLogout(String userId, Channel session, int beKickoutCode) {
        log.info("[RBChat]用户断开/注销IM服务器连接--" + "用户ID:" + userId);
        //ChatLogicManager.processUserLogoutAction(userId, session, beKickoutCode);

    }

    /**
     * 收到客户端发送给“其它客户端”的数据回调通知（即：消息路径为“C2C”的消息）前的处理逻辑。
     * <p>
     * <b>本方法的默认实现</b>：<font color="green">当开发者不需要本方法进行额外逻辑处理时，请直接返回true即可！</font>
     * <p>
     * <b>本方法的典型用途</b>：开发者可在本方法中实现如：用户聊天内容的鉴黄、过滤、篡改等等，把内容审读权限交给开发者，就看怎么用了。
     *
     * @param p       消息/指令的完整协议包对象
     * @param session 消息发送者的“会话”引用（也就是客户端的网络连接对象）
     * @return true表示经过本方法后将正常进入 {@link #onTransferMessage4C2C(Protocal)}继续正常逻辑  ，false表示该条指令将不会继续处理（直接被丢弃）
     * @see #onTransferMessage4C2C(Protocal)
     * @since 8.1
     */
    @Override
    public boolean onTransferMessage4C2SBefore(Protocal p, Channel session) {
        log.info("用户向服务器发送消息前置操作");

        return true;
    }

    /**
     * 收到客户端发送给“服务端”的数据回调通知（即：消息路径为“C2S”的消息）前的处理逻辑。
     * <p>
     * <b>本方法的默认实现</b>：<font color="green">当开发者不需要本方法进行额外逻辑处理时，请直接返回true即可！</font>
     * <p>
     * <b>本方法的典型用途</b>：开发者可在本方法中实现如：用户聊天内容的鉴黄、过滤、篡改等等，把内容审读权限交给开发者，就看怎么用了。
     *
     * @param p       消息/指令的完整协议包对象
     * @param session 消息发送者的“会话”引用（也就是客户端的网络连接对象）
     * @return true表示经过本方法后将正常进入 {@link #onTransferMessage4C2S(Protocal, Channel)}继续正常逻辑  ，false表示该条指令将不会继续处理（直接被丢弃）
     * @see #onTransferMessage4C2S(Protocal, Channel)
     * @since 8.1
     */
    @Override
    public boolean onTransferMessage4C2CBefore(Protocal p, Channel session) {
        // log.info("用户发送给用户前置操作C2C");
        //通过rpc 调用logic
        //Chat中会生成2个消息发到Kafka（发件箱和收件箱）
        //todo 判断用户是否集群在线。集群在线走集群发送。不在线走离线。返回fasle
        return true;
    }

    /**
     * 通用数据回调方法定义（客户端发给服务端的（即接收user_id="0"））.
     * <p>
     * MobileIMSDK在收到客户端向user_id=0(即接收目标是服务器)的情况下通过
     * 本方法的回调通知上层。上层通常可在本方法中实现如：添加好友请求等业务实现。
     *
     * @param p
     * @param session 此客户端连接对应的MINA会话
     * @return true表示本方法已成功处理完成，否则表示未处理成功。此返回值目前框架中并没有特殊意义，仅作保留吧
     */
    @Override
    public boolean onTransferMessage4C2S(Protocal p, Channel session) {
        log.info("用户发送给服务器,临时聊天,群聊消息转发等，添加好友消息，拒绝好友消息，");
        // 接收者uid
        String userId = p.getTo();
        // 发送者uid
        String from_user_id = p.getFrom();
        // 消息或指令内容
        String dataContent = p.getDataContent();
        // 消息或指令指纹码（即唯一ID）
        String fingerPrint = p.getFp();
        // 【重要】用户定义的消息或指令协议类型（开发者可据此类型来区分具体的消息或指令）
        int typeu = p.getTypeu();
        log.debug("【DEBUG_回调通知】[typeu=" + typeu + "]收到了客户端" + from_user_id + "发给服务端的消息：str=" + dataContent);
        //处理系统消息
        if ("0".equals(userId)) {
            try {
                final String msg = dataContent;
                log.debug("[RBChat]此消息接收对象为\"0\"(即server自已)。");
                // 处理用户A需要由服务端转发的临时聊天消息（给接收方B）
                if (typeu == UserProtocalsType.MT42_OF_TEMP$CHAT$MSG_A$TO$SERVER) {
                    //临时消息。类似钉钉组织架构下聊天
                    //return ChatLogicManager.processMT42_OF_TEMP$CHAT$MSG_A$TO$SERVER(fingerPrint, msg);
                }

                // 处理用户A需要由服务端转发的群聊消息
                if (typeu == UserProtocalsType.MT44_OF_GROUP$CHAT$MSG_A$TO$SERVER) {
                    // ChatLogicManager4Group.processMT44_OF_GROUP$CHAT$MSG_A$TO$SERVER(fingerPrint, msg);
                }
                // 处理用户A发起的添加好友请求
                if (typeu == UserProtocalsType.MT05_OF_ADD_FRIEND_REQUEST_A$TO$SERVER) {
                    // 解析添加好友请求原数据（客户端A发过来的）
                    //  CMDBody4AddFriendRequest addFriendMetaFromClient = MessageHelper.pareseAddFriendRequestMessage(dataContent);
                    // 进入处理逻辑并返回结果
                    // return ChatLogicManager.processMT05_OF_ADD_FRIEND_REQUEST_A$TO$SERVER(addFriendMetaFromClient);
                }
                // 被添加者【同意】加好友请求的消息（由B发给服务端）
                else if (typeu == UserProtocalsType.MT08_OF_PROCESS_ADD$FRIEND$REQ_B$TO$SERVER_AGREE) {
                    // 解析被添加者同意加好友请求原数据（客户端A发过来的）
//                    CMDBody4ProcessFriendRequest processFriendMetaFromClient =
//                            MessageHelper.pareseProcessAdd$Friend$Req_B$To$Server_AGREEMessage(dataContent);
//                    // 进入处理逻辑并返回结果
//                    return ChatLogicManager.processMT08_OF_PROCESS_ADD$FRIEND$REQ_B$TO$SERVER_AGREE(processFriendMetaFromClient);
                }

                // 被添加者【拒绝】加好友请求的消息（由B发给服务端）
                else if (typeu == UserProtocalsType.MT09_OF_PROCESS_ADD$FRIEND$REQ_B$TO$SERVER_REJECT) {
                    // 解析被添加者拒绝加好友请求原数据（客户端A发过来的）
//                    CMDBody4ProcessFriendRequest processFriendMetaFromClient =
//                            MessageHelper.pareseProcessAdd$Friend$Req_B$To$Server_REJECTMessage(dataContent);
//                    // 进入处理逻辑并返回结果
//                    ChatLogicManager.processMT09_OF_PROCESS_ADD$FRIEND$REQ_B$TO$SERVER_REJECT(processFriendMetaFromClient);
                }

            } catch (Exception e) {
                log.warn(e.getMessage(), e);
            }

        }


        return true;
    }

    /**
     * 通道数据回调函数定义（客户端发给客户端的（即接收方user_id不为“0”的情况））.
     * <p>
     * <b>注意：</b>本方法当且仅当在数据被服务端成功在线发送出去后被回调调用.
     * <p>
     * 上层通常可在本方法中实现用户聊天信息的收集，以便后期监控分析用户的行为等^_^。
     * <p>
     * 提示：如果开启消息QoS保证，因重传机制，本回调中的消息理论上有重复的可能，请以参数 #fingerPrint
     * 作为消息的唯一标识ID进行去重处理。
     *
     * @param p
     */
    @Override
    public void onTransferMessage4C2C(Protocal p) {
        log.info("用户发送给用户");
        iKafkaProducerService.sendMessage("s2s_chat", p.getDataContent());
        iKafkaProducerService.sendMessage("s2m_chat", p.getDataContent());

        // 接收者uid
        String userId = p.getTo();
        // 发送者uid
        String from_user_id = p.getFrom();
        // 消息或指令内容
        String dataContent = p.getDataContent();
        // 消息或指令指纹码（即唯一ID）
        String fingerPrint = p.getFp();
        // 【重要】用户定义的消息或指令协议类型（开发者可据此类型来区分具体的消息或指令）
        int typeu = p.getTypeu();
//		logger.debug("收到了客户端"+from_user_id+"发给客户端"+userId+"的消息：str="+dataContent);
        // 【一对一好友聊天消息回调(可能来自桥接或本实例模式)】
        if (typeu == UserProtocalsType.MT03_OF_CHATTING_MESSAGE) {
            // ** 【用户消息收集】收集用户的聊天消息，以供后台进行用户行为分析，
            // MessageRecordManager.colectChattingMsgAsync(dataContent, fingerPrint);
        }
        /******************************* 以下消息回调通知，只可能来自桥接模式 START *************************************/
        /** 因为根据MobileIMSDK APP端服务端器的设计逻辑，S2C消息本实例发送成功时，
         *  由发送者在应用层处理自行决定余下逻辑（如存聊天记录），而不是由框架层的本回调处理 */
        // 【群聊消息回调(只可能来自桥接模式)】
        // 不要在此记录群聊消息（因为群聊消息应只记录发送的，而不记录接收的，
        // 否则1000个群员就得存1000条记录，而本回调就是接收的消息，所以不应在此存）。
        // 群聊消息的聊天记录，应在发送的时候存（谁发谁存，而不是谁收谁存，详见设计备忘录！）。
        if (typeu == UserProtocalsType.MT45_OF_GROUP$CHAT$MSG_SERVER$TO$B) {// FIXME: 以下代码仅用于Debug时，并无实际业务逻辑，可以删除！
            // 什么也不用做
//            log.debug("[RBChat]【Bridge2C_CallBack-群聊】[typeu=" + p.getTypeu() + "], 客户端"
//                    + p.getFrom() + " 发给客户端"+ p.getTo() + " 的实时消息：str=[" + dataContent
//                    + "] 【根据S2C类型消息记录设计逻辑，此回调中无需存记录，忽略之！】");
        }

        // 【临时/陌生人聊天消息回调(只可能来自桥接模式)】
        // 陌生人消息不要在这里收集记录，根据目前的设计原则，s2c消息记录一律由发送者处理(而不是接收者)，详见设计备忘录
        if (typeu == UserProtocalsType.MT43_OF_TEMP$CHAT$MSG_SERVER$TO$B) {// FIXME: 以下代码仅用于Debug时，并无实际业务逻辑，可以删除！

            // 什么也不用做
//            LoggerFactory.getLog().debug("[RBChat]【Bridge2C_CallBack-陌生人聊】[typeu=" + p.getTypeu() + "], 客户端"
//                    + p.getFrom() + " 发给客户端"+ p.getTo() + " 的实时消息：str=[" + dataContent
//                    + "] 【根据S2C类型消息记录设计逻辑，此回调中无需存记录，忽略之！】");
        } else {
            log.debug("[RBChat]【C2C_CallBack】[typeu=" + typeu + "], 客户端" + from_user_id + " 发给客户端"
                    + userId + " 的消息：str=[" + dataContent + "] 【不支持的typeu类型，无需进行聊天记录存储！！】");
        }

    }

    /**
     * 服务端在进行消息发送时，当对方在线但实时发送失败、以及其它各种问题导致消息并没能正常发出时
     * ，将无条件走本回调通知。
     * <p>
     * 注意：本方法当且仅当在数据被服务端<u>在线发送</u>失败后被回调调用.
     * <p>
     * <b>此方法存的意义何在？</b><br>
     * 发生此种情况的场景可能是：对方确实不在线（那么此方法里就可以作为离线消息处理了）、
     * 或者在发送时判断对方是在线的但服务端在发送时却没有成功（这种情况就可能是通信错误
     * 或对方非正常通出但尚未到达会话超时时限）。<br><u>应用层在此方法里实现离线消息的处理即可！</u>
     *
     * @param p
     * @return true表示应用层已经处理了离线消息（如果该消息有QoS机制，则服务端将代为发送一条伪应答包
     * （伪应答仅意味着不是接收方的实时应答，而只是存储到离线DB中，但在发送方看来也算是被对方收到，只是延
     * 迟收到而已（离线消息嘛））），否则表示应用层没有处理（如果此消息有QoS机制，则发送方在QoS重传机制超时
     * 后报出消息发送失败的提示）
     */
    @Override
    public boolean onTransferMessage_RealTimeSendFaild(Protocal p) {
        log.info("消息发送失败后置处理 or 离线消息");
        return onTransferMessage_RealTimeSendFaild_impl(p, false);

    }

    /**
     * 集群发送通道交给上层用户实现。
     *
     * @param p
     * @return
     */
    @Override
    public boolean onTransferMessage_ClusterSender(Protocal p) {
        //处理集群消息 投递给用户收件箱 和消息网关
        iKafkaProducerService.sendMessage("s2s_chat", p.toGsonString());
        iKafkaProducerService.sendMessage("s2m_chat", p.toGsonString());
        return true;
    }

    /**
     * onTransferMessage_RealTimeSendFaild的实现方法。
     *
     * @param p                                  原始数据
     * @param forceFriendChattingNoRecordHistory true表示强制不存储一对一好友聊天的聊天记录，否则存储
     * @return true表示已妥善处理离线消息
     */
    public boolean onTransferMessage_RealTimeSendFaild_impl(Protocal p, boolean forceFriendChattingNoRecordHistory) {
        // 接收者uid
        String userId = p.getTo();
        // 发送者uid
        String from_user_id = p.getFrom();
        // 消息或指令内容
        String dataContent = p.getDataContent();
        // 消息或指令指纹码（即唯一ID）
        String fingerPrint = p.getFp();
        // 【重要】用户定义的消息或指令协议类型（开发者可据此类型来区分具体的消息或指令）
        int typeu = p.getTypeu();

        // 此离线消息是否需要记录到聊天记录中
        boolean needRecordToChattingHistory = false;

        boolean processed = false;
        switch (typeu) {
            // 目前C2C的消息需要离线处理的，也就是用户的一对一好友聊天消息了
            case UserProtocalsType.MT03_OF_CHATTING_MESSAGE: {
//				processed = OfflineMessageManager.processOfflineMessage(p, false);

                // 强制不存储聊天记录（因为一对一好友聊天走的是c2c模式，它的聊天记录在QoS模
                // 式下已走 onTransferMessage4C2C()回调进行了存储，此参数就是为了在QoS场景下
                // 不需要重复存储聊天记录了）
                if (forceFriendChattingNoRecordHistory) {
                    needRecordToChattingHistory = false;
                } else {
                    /** 根据MobileIMSDK和Web端服务端器的设计逻辑，C2C好友聊天消息在接收方离线情况下的聊天记录处理，
                     * 需要在离线回调中处理（而不是在onTransBuffer_C2C_CallBack中），详见“RainbowChat_pro_v4关键设计思路备忘.txt” */
                    needRecordToChattingHistory = true;
                }
//
//				break;
            }

            /******************************* 以下离线处理，只可能来自桥接模式 START *************************************/
            /** 因为根据MobileIMSDK APP端服务端器的设计逻辑，S2C消息本实例发送失败时，由发送者在应用层处理，而不是由框架层的本回调处理 */
            //* 临时聊天消息的离线处理逻辑
            case UserProtocalsType.MT43_OF_TEMP$CHAT$MSG_SERVER$TO$B:
                //* 群组聊天消息的离线处理逻辑
            case UserProtocalsType.MT45_OF_GROUP$CHAT$MSG_SERVER$TO$B:
                //* 各群聊指令通知的离线处理逻辑
            case UserProtocalsType.MT46_OF_GROUP$SYSCMD_MYSELF$BE$INVITE_FROM$SERVER:
            case UserProtocalsType.MT47_OF_GROUP$SYSCMD_COMMON$INFO_FROM$SERVER:
            case UserProtocalsType.MT48_OF_GROUP$SYSCMD_DISMISSED_FROM$SERVER:
            case UserProtocalsType.MT49_OF_GROUP$SYSCMD_YOU$BE$KICKOUT_FROM$SERVER:
            case UserProtocalsType.MT50_OF_GROUP$SYSCMD_SOMEONEB$REMOVED_FROM$SERVER:
            case UserProtocalsType.MT51_OF_GROUP$SYSCMD_GROUP$NAME$CHANGED_FROM$SERVER: {
                // processed = OfflineMessageManager.processOfflineMessage(p, false);
                break;
            }
            /******************************* 以上离线处理，只可能来自桥接模式 END *************************************/

            default: {
                log.debug("[RBChat]【C2C】客户端" + from_user_id + "发给" + userId + "的消息类型typeu为"
                        + typeu + "的消息或指令：str=" + dataContent + "没有在离线处理逻辑内，本次离线回调被忽略哦！");
                processed = true;
                break;
            }
        }

        // 离线消息也要记录到聊天记录中
        if (needRecordToChattingHistory) {
            try {
                // ** 【用户消息收集并落库】收集用户的聊天消息，以供后台进行用户行为分析，
                //MessageRecordManager.colectChattingMsgAsync(p.getDataContent(), p.getFp());
            } catch (Exception e) {
                log.warn(e.getMessage(), e);
            }
        }

        return processed;
    }


    @Override
    public void onTransferMessage4C2C_AfterBridge(Protocal protocal) {
    }

}